In this workshop, we will explore how to create tables using the gt and gtExtras packages, utilizing the pizza place dataset. We will start with a basic table using base R to display pizza information, then enhance our visualization using the gt package to create a more polished and user-friendly table. Finally, we will upgrade our visualization further with the gtExtras package, adding even more features and styles to improve the overall presentation of our pizza menu.
Let’s look at our pizza sales dataset:
# Load and assign the pizza dataset to an object
data("pizzaplace")
pizza_data <- pizzaplace
head(pizza_data)
## # A tibble: 6 × 7
## id date time name size type price
## <chr> <chr> <chr> <chr> <chr> <chr> <dbl>
## 1 2015-000001 2015-01-01 11:38:36 hawaiian M classic 13.2
## 2 2015-000002 2015-01-01 11:57:40 classic_dlx M classic 16
## 3 2015-000002 2015-01-01 11:57:40 mexicana M veggie 16
## 4 2015-000002 2015-01-01 11:57:40 thai_ckn L chicken 20.8
## 5 2015-000002 2015-01-01 11:57:40 five_cheese L veggie 18.5
## 6 2015-000002 2015-01-01 11:57:40 ital_supr L supreme 20.8
First, let’s create a summary table using basic R:
pizza_summary <- pizza_data %>%
group_by(type, size) %>%
summarize(
total_sales = sum(price),
num_orders = n(),
avg_price = mean(price),
.groups = 'drop'
)
# Display as basic table
print(pizza_summary)
## # A tibble: 14 × 5
## type size total_sales num_orders avg_price
## <chr> <chr> <dbl> <int> <dbl>
## 1 chicken L 102339 4932 20.8
## 2 chicken M 65224. 3894 16.8
## 3 chicken S 28356 2224 12.8
## 4 classic L 74518. 4057 18.4
## 5 classic M 60582. 4112 14.7
## 6 classic S 69870. 6139 11.4
## 7 classic XL 14076 552 25.5
## 8 classic XXL 1007. 28 36.0
## 9 supreme L 94258. 4564 20.7
## 10 supreme M 66475 4046 16.4
## 11 supreme S 47464. 3377 14.1
## 12 veggie L 104203. 5403 19.3
## 13 veggie M 57101 3583 15.9
## 14 veggie S 32387. 2663 12.2
Let’s start with a simple gt table:
pizza_summary %>%
gt(
groupname_col = "type"
)
| size | total_sales | num_orders | avg_price |
|---|---|---|---|
| chicken | |||
| L | 102339.00 | 4932 | 20.75000 |
| M | 65224.50 | 3894 | 16.75000 |
| S | 28356.00 | 2224 | 12.75000 |
| classic | |||
| L | 74518.50 | 4057 | 18.36788 |
| M | 60581.75 | 4112 | 14.73292 |
| S | 69870.25 | 6139 | 11.38137 |
| XL | 14076.00 | 552 | 25.50000 |
| XXL | 1006.60 | 28 | 35.95000 |
| supreme | |||
| L | 94258.50 | 4564 | 20.65261 |
| M | 66475.00 | 4046 | 16.42981 |
| S | 47463.50 | 3377 | 14.05493 |
| veggie | |||
| L | 104202.70 | 5403 | 19.28608 |
| M | 57101.00 | 3583 | 15.93665 |
| S | 32386.75 | 2663 | 12.16175 |
Now let’s add currency and number formatting:
pizza_summary %>%
gt(
groupname_col = "type"
) %>%
fmt_currency(
columns = c(total_sales, avg_price),
currency = "USD"
) %>%
fmt_number(
columns = num_orders,
decimals = 0
)
| size | total_sales | num_orders | avg_price |
|---|---|---|---|
| chicken | |||
| L | $102,339.00 | 4,932 | $20.75 |
| M | $65,224.50 | 3,894 | $16.75 |
| S | $28,356.00 | 2,224 | $12.75 |
| classic | |||
| L | $74,518.50 | 4,057 | $18.37 |
| M | $60,581.75 | 4,112 | $14.73 |
| S | $69,870.25 | 6,139 | $11.38 |
| XL | $14,076.00 | 552 | $25.50 |
| XXL | $1,006.60 | 28 | $35.95 |
| supreme | |||
| L | $94,258.50 | 4,564 | $20.65 |
| M | $66,475.00 | 4,046 | $16.43 |
| S | $47,463.50 | 3,377 | $14.05 |
| veggie | |||
| L | $104,202.70 | 5,403 | $19.29 |
| M | $57,101.00 | 3,583 | $15.94 |
| S | $32,386.75 | 2,663 | $12.16 |
Let’s improve the presentation with better headers:
pizza_summary %>%
gt(
groupname_col = "type"
) %>%
fmt_currency(
columns = c(total_sales, avg_price),
currency = "USD"
) %>%
fmt_number(
columns = num_orders,
decimals = 0
) %>%
tab_header(
title = "Pizza Sales Analysis",
subtitle = "Sales breakdown by type and size"
) %>%
cols_label(
total_sales = "Total Sales",
num_orders = "Number of Orders",
avg_price = "Average Price",
size = "Pizza Size"
)
| Pizza Sales Analysis | |||
| Sales breakdown by type and size | |||
| Pizza Size | Total Sales | Number of Orders | Average Price |
|---|---|---|---|
| chicken | |||
| L | $102,339.00 | 4,932 | $20.75 |
| M | $65,224.50 | 3,894 | $16.75 |
| S | $28,356.00 | 2,224 | $12.75 |
| classic | |||
| L | $74,518.50 | 4,057 | $18.37 |
| M | $60,581.75 | 4,112 | $14.73 |
| S | $69,870.25 | 6,139 | $11.38 |
| XL | $14,076.00 | 552 | $25.50 |
| XXL | $1,006.60 | 28 | $35.95 |
| supreme | |||
| L | $94,258.50 | 4,564 | $20.65 |
| M | $66,475.00 | 4,046 | $16.43 |
| S | $47,463.50 | 3,377 | $14.05 |
| veggie | |||
| L | $104,202.70 | 5,403 | $19.29 |
| M | $57,101.00 | 3,583 | $15.94 |
| S | $32,386.75 | 2,663 | $12.16 |
Now let’s add some visual enhancements:
pizza_summary %>%
gt(
groupname_col = "type"
) %>%
fmt_currency(
columns = c(total_sales, avg_price),
currency = "USD"
) %>%
fmt_number(
columns = num_orders,
decimals = 0
) %>%
tab_header(
title = "Pizza Sales Analysis",
subtitle = "Sales breakdown by type and size"
) %>%
cols_label(
total_sales = "Total Sales",
num_orders = "Number of Orders",
avg_price = "Average Price",
size = "Pizza Size"
) %>%
gt_theme_538() %>%
data_color(
columns = c(total_sales),
colors = scales::col_numeric(
palette = c("white", "#1F77B4"),
domain = NULL
)
)
| Pizza Sales Analysis | |||
| Sales breakdown by type and size | |||
| Pizza Size | Total Sales | Number of Orders | Average Price |
|---|---|---|---|
| chicken | |||
| L | $102,339.00 | 4,932 | $20.75 |
| M | $65,224.50 | 3,894 | $16.75 |
| S | $28,356.00 | 2,224 | $12.75 |
| classic | |||
| L | $74,518.50 | 4,057 | $18.37 |
| M | $60,581.75 | 4,112 | $14.73 |
| S | $69,870.25 | 6,139 | $11.38 |
| XL | $14,076.00 | 552 | $25.50 |
| XXL | $1,006.60 | 28 | $35.95 |
| supreme | |||
| L | $94,258.50 | 4,564 | $20.65 |
| M | $66,475.00 | 4,046 | $16.43 |
| S | $47,463.50 | 3,377 | $14.05 |
| veggie | |||
| L | $104,202.70 | 5,403 | $19.29 |
| M | $57,101.00 | 3,583 | $15.94 |
| S | $32,386.75 | 2,663 | $12.16 |
Let’s create a different view focusing on pizza types:
pizza_data %>%
# First calculate the total number of orders
mutate(total_orders = n()) %>%
group_by(type) %>%
summarize(
total_sales = sum(price),
num_orders = n(),
avg_price = mean(price),
pct_total = num_orders/first(total_orders) * 100 # Fixed percentage calculation
) %>%
ungroup() %>%
gt() %>%
# Basic formatting
fmt_currency(columns = c(total_sales, avg_price), currency = "USD") %>%
fmt_number(columns = num_orders, decimals = 0) %>%
fmt_percent(columns = pct_total, decimals = 1) %>%
# Enhanced styling
gt_theme_nytimes() %>%
# Add mini charts
gt_plt_bar_pct(column = pct_total, scaled = TRUE) %>%
# Enhanced header
tab_header(
title = md("**🍕 Pizza Sales Performance Analysis**"),
subtitle = md("*Distribution of sales across pizza types*")
) %>%
# Column formatting
cols_width(
type ~ px(150),
total_sales ~ px(200)
) %>%
# Conditional formatting
tab_style(
style = list(
cell_fill(color = "#e8f4f8"),
cell_text(weight = "bold")
),
locations = cells_body(
columns = num_orders,
rows = num_orders > mean(num_orders)
)
) %>%
# Color gradient for total sales
data_color(
columns = total_sales,
colors = scales::col_numeric(
palette = c("white", "steelblue"),
domain = NULL
)
) %>%
# Footer elements
tab_footnote(
footnote = "Higher than average sales",
locations = cells_body(
columns = total_sales,
rows = total_sales > mean(total_sales)
)
) %>%
tab_source_note(
source_note = md("*Data source: Pizza Place Sales*")
) %>%
# Table options
tab_options(
heading.title.font.size = px(24),
heading.subtitle.font.size = px(16),
heading.background.color = "#f6f6f6",
column_labels.background.color = "#e6e6e6",
table.border.top.width = px(3),
table.border.top.color = "#4A4A4A",
column_labels.font.weight = "bold",
data_row.padding = px(12),
source_notes.font.size = px(10)
)
| 🍕 Pizza Sales Performance Analysis | ||||
| Distribution of sales across pizza types | ||||
| type | total_sales | num_orders | avg_price | pct_total |
|---|---|---|---|---|
| chicken | $195,919.50 | 11,050 | $17.73 | |
| classic | 1 $220,053.10 | 14,888 | $14.78 | |
| supreme | 1 $208,197.00 | 11,987 | $17.37 | |
| veggie | $193,690.45 | 11,649 | $16.63 | |
| Data source: Pizza Place Sales | ||||
| 1 Higher than average sales | ||||
pizza_data %>%
group_by(type) %>%
summarize(
total_sales = sum(price),
num_orders = n(),
avg_price = mean(price),
sales_trend = list(tapply(price, date, mean))
) %>%
gt() %>%
# Make sure currency formatting is applied after theme
gt_theme_dark() %>%
# Format numbers
fmt_currency(
columns = c(total_sales, avg_price),
currency = "USD",
placement = "right",
decimals = 2,
use_subunits = TRUE
) %>%
fmt_number(
columns = num_orders,
decimals = 0,
use_seps = TRUE
) %>%
# Sparkline and bar plots
gt_plt_sparkline(sales_trend, label = FALSE) %>%
# Remove bar plot for total_sales to ensure numbers are visible
# gt_plt_bar(column = total_sales, color = "steelblue", width = 65) %>%
# Header styling
tab_header(
title = md("🌙 Night Mode Sales Dashboard"),
subtitle = md("*Visualization of pizza sales*")
) %>%
# Column labels
cols_label(
type = md("**Pizza Type**"),
total_sales = md("**Total Revenue**"),
num_orders = md("**Orders**"),
avg_price = md("**Avg. Price**"),
sales_trend = md("**Sales Trend**")
) %>%
# Column alignment
cols_align(
align = "left",
columns = type
) %>%
cols_align(
align = "center",
columns = c(num_orders, sales_trend)
) %>%
cols_align(
align = "right",
columns = c(total_sales, avg_price)
) %>%
# Column width adjustments
cols_width(
type ~ px(150),
total_sales ~ px(180),
num_orders ~ px(120),
avg_price ~ px(120),
sales_trend ~ px(150)
) %>%
# Add borders and styling
tab_style(
style = list(
cell_borders(
sides = c("left", "right"),
color = "#404040",
weight = px(1)
)
),
locations = cells_body()
) %>%
# Highlight highest values
tab_style(
style = list(
cell_fill(color = "#2d4a54"),
cell_text(weight = "bold", color = "white") # Ensure text is visible
),
locations = cells_body(
columns = total_sales,
rows = total_sales == max(total_sales)
)
) %>%
# Add row striping
opt_row_striping() %>%
# Table styling
tab_options(
table.background.color = "#1a1a1a",
heading.title.font.size = px(28),
heading.subtitle.font.size = px(18),
heading.padding = px(20),
column_labels.background.color = "#2d2d2d",
column_labels.font.weight = "bold",
column_labels.padding = px(15),
table.border.top.style = "solid",
table.border.top.width = px(3),
table.border.top.color = "white",
table.border.bottom.color = "white",
table.border.bottom.width = px(3),
column_labels.border.bottom.color = "white",
column_labels.border.bottom.width = px(2),
data_row.padding = px(12),
row.striping.background_color = "#2a2a2a",
table.font.color = "white",
table.font.size = px(14)
) %>%
# Add source note
tab_source_note(
source_note = md("*Data source: Pizza Place Sales Dataset*")
)
| 🌙 Night Mode Sales Dashboard | ||||
| Visualization of pizza sales | ||||
| Pizza Type | Total Revenue | Orders | Avg. Price | Sales Trend |
|---|---|---|---|---|
| chicken | 195,919.50$ | 11,050 | 17.73$ | |
| classic | 220,053.10$ | 14,888 | 14.78$ | |
| supreme | 208,197.00$ | 11,987 | 17.37$ | |
| veggie | 193,690.45$ | 11,649 | 16.63$ | |
| Data source: Pizza Place Sales Dataset | ||||
pizza_data %>%
group_by(type) %>%
summarize(
total_sales = sum(price),
num_orders = n(),
avg_price = mean(price)
) %>%
ungroup() %>%
gt(
groupname_col = "type"
) %>%
fmt_currency(
columns = c(total_sales, avg_price),
currency = "USD"
) %>%
fmt_number(
columns = num_orders,
decimals = 0
) %>%
grand_summary_rows(
columns = total_sales,
fns = list(
"Total" = ~sum(.)
),
fmt = ~ fmt_currency(., currency = "USD")
) %>%
grand_summary_rows(
columns = num_orders,
fns = list(
"Total" = ~sum(.)
),
fmt = ~ fmt_number(., decimals = 0)
)
| total_sales | num_orders | avg_price | |
|---|---|---|---|
| chicken | |||
| $195,919.50 | 11,050 | $17.73 | |
| classic | |||
| $220,053.10 | 14,888 | $14.78 | |
| supreme | |||
| $208,197.00 | 11,987 | $17.37 | |
| veggie | |||
| $193,690.45 | 11,649 | $16.63 | |
| Total | $817,860.05 | 49,574 | — |
# First create the pricing structure with features
pizza_pricing <- pizza_data %>%
group_by(type, size) %>%
summarize(
price = mean(price),
.groups = 'drop'
) %>%
group_by(type) %>%
summarize(
basic = min(price),
medium = median(price),
large = max(price)
) %>%
mutate(
type_display = case_when(
type == "chicken" ~ "🍗 Chicken Lovers",
type == "classic" ~ "🍕 Classic Choice",
type == "supreme" ~ "👑 Supreme Selection",
type == "veggie" ~ "🥬 Veggie Delight"
),
features = case_when(
type == "chicken" ~ "✓ Fresh chicken ✓ Special herbs ✓ Premium cheese",
type == "classic" ~ "✓ Traditional sauce ✓ Classic toppings ✓ Family favorite",
type == "supreme" ~ "✓ Premium toppings ✓ Extra cheese ✓ Special blend",
type == "veggie" ~ "✓ Fresh vegetables ✓ Light cheese ✓ Herb-infused"
)
)
# Create the enhanced pricing table
pizza_pricing %>%
select(type_display, features, basic, medium, large) %>%
gt() %>%
tab_header(
title = md("🌟 **Premium Pizza Price Plans** 🌟"),
subtitle = md("*Explore our range of artisanal pizzas crafted just for you*")
) %>%
cols_label(
type_display = "",
features = md("**Includes**"),
basic = md("**Basic Plan**"),
medium = md("**Premium Plan**"),
large = md("**Deluxe Plan**")
) %>%
fmt_currency(
columns = c(basic, medium, large),
currency = "USD"
) %>%
# Style plan headers
tab_style(
style = list(
cell_fill(color = "#4287f5"),
cell_text(color = "white", weight = "bold", size = px(14)),
cell_borders(sides = "all", color = "white", weight = px(2))
),
locations = cells_column_labels(columns = basic)
) %>%
tab_style(
style = list(
cell_fill(color = "#2ecc71"),
cell_text(color = "white", weight = "bold", size = px(14)),
cell_borders(sides = "all", color = "white", weight = px(2))
),
locations = cells_column_labels(columns = medium)
) %>%
tab_style(
style = list(
cell_fill(color = "#e74c3c"),
cell_text(color = "white", weight = "bold", size = px(14)),
cell_borders(sides = "all", color = "white", weight = px(2))
),
locations = cells_column_labels(columns = large)
) %>%
# Style pizza types
tab_style(
style = list(
cell_text(weight = "bold", size = px(14)),
cell_fill(color = "#f8f9fa"),
cell_borders(sides = "all", color = "#dee2e6", weight = px(1))
),
locations = cells_body(columns = type_display)
) %>%
# Add borders
tab_style(
style = list(
cell_borders(sides = "all", color = "#dee2e6", weight = px(2))
),
locations = cells_body()
) %>%
# Alignment
cols_align(
align = "center",
columns = c(basic, medium, large)
) %>%
cols_align(
align = "left",
columns = c(type_display, features)
) %>%
# Column widths
cols_width(
type_display ~ px(180),
features ~ px(250),
everything() ~ px(150)
) %>%
# Add plan features spanner
tab_spanner(
label = md("**🎯 Select Your Perfect Plan 🎯**"),
columns = c(basic, medium, large)
) %>%
# Style the prices
tab_style(
style = list(
cell_text(weight = "bold", size = px(16)),
cell_fill(color = "#ffffff")
),
locations = cells_body(
columns = c(basic, medium, large)
)
) %>%
# Add row striping
opt_row_striping(row_striping = TRUE) %>%
# Table styling
tab_options(
heading.background.color = "#f8f9fa",
heading.title.font.size = px(28),
heading.subtitle.font.size = px(16),
heading.padding = px(20),
column_labels.background.color = "#f8f9fa",
table.border.top.width = px(3),
table.border.bottom.width = px(3),
table.border.top.color = "#4e73df",
table.border.bottom.color = "#4e73df",
data_row.padding = px(15),
row.striping.background_color = "#f9f9f9",
heading.title.font.weight = "bold"
) %>%
# Add features styling
tab_style(
style = list(
cell_borders(sides = "all", color = "#dee2e6", weight = px(1)),
cell_fill(color = "#e8f4f8")
),
locations = cells_body(columns = features)
) %>%
# Add badges/footnotes
tab_footnote(
footnote = md("🔥 **Most Popular Choice**"),
locations = cells_column_labels(columns = medium)
) %>%
tab_footnote(
footnote = md("⭐ **Best Value**"),
locations = cells_column_labels(columns = large)
) %>%
# Source notes
tab_source_note(
source_note = md(
"✨ *All plans include our signature sauce and premium cheese* |
🛵 *Free delivery on orders over $30* |
💫 *Customization available for all plans*"
)
)
| 🌟 Premium Pizza Price Plans 🌟 | ||||
| Explore our range of artisanal pizzas crafted just for you | ||||
| Includes |
🎯 Select Your Perfect Plan 🎯
|
|||
|---|---|---|---|---|
| Basic Plan | Premium Plan1 | Deluxe Plan2 | ||
| 🍗 Chicken Lovers | ✓ Fresh chicken ✓ Special herbs ✓ Premium cheese | $12.75 | $16.75 | $20.75 |
| 🍕 Classic Choice | ✓ Traditional sauce ✓ Classic toppings ✓ Family favorite | $11.38 | $18.37 | $35.95 |
| 👑 Supreme Selection | ✓ Premium toppings ✓ Extra cheese ✓ Special blend | $14.05 | $16.43 | $20.65 |
| 🥬 Veggie Delight | ✓ Fresh vegetables ✓ Light cheese ✓ Herb-infused | $12.16 | $15.94 | $19.29 |
| ✨ All plans include our signature sauce and premium cheese | 🛵 Free delivery on orders over $30 | 💫 Customization available for all plans |
||||
| 1 🔥 Most Popular Choice | ||||
| 2 ⭐ Best Value | ||||